package org.rufolfheszele.andpixelbyandpixel;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.nio.ByteBuffer;
import android.graphics.Point;

// TODO: What about ntohl and nhtol in Java?

public class NetworkeventData
{
	public enum DataType
	{
		INVALID(0),
		CONNECTING(1),
		CONNECTING_REJECT(2),
		CONNECTING_ACK(3),		// This will be sent by the client, when it is ready to start the game
		CONFIGURATION(4),
		CONFIGURATION_ACK(5),
		RESTART(6),
		NEW_LEVEL(7),
		NEW_LEVEL_ACK(8),
		NEW_LEVEL_REJECT(9),
		MOUSE_DOWN(10),
		MOUSE_UP(11),
		MOUSE_MOTION(12),
		MESSAGING(13),
		DOWNLOADING_START(14),
		DOWNLOADING_START_ACK(15),
		DOWNLOADING_START_REJECT(16),
		DOWNLOADING_DATA(17),
		DOWNLOADING_DATA_ACK(18),
		DOWNLOADING_END(19),
		KICK_OFF(20);
		
		public static DataType getEnumValue(int i)
		{
			switch(i)
			{
				case 0: return INVALID; 
				case 1: return CONNECTING;
				case 2: return CONNECTING_REJECT;
				case 3: return CONNECTING_ACK;
				case 4: return CONFIGURATION;
				case 5: return CONFIGURATION_ACK;
				case 6: return RESTART;
				case 7: return NEW_LEVEL;
				case 8: return NEW_LEVEL_ACK;
				case 9: return NEW_LEVEL_REJECT;
				case 10: return MOUSE_DOWN;
				case 11: return MOUSE_UP;
				case 12: return MOUSE_MOTION;
				case 13: return MESSAGING;
				case 14: return DOWNLOADING_START;
				case 15: return DOWNLOADING_START_ACK;
				case 16: return DOWNLOADING_START_REJECT;
				case 17: return DOWNLOADING_DATA;
				case 18: return DOWNLOADING_DATA_ACK;
				case 19: return DOWNLOADING_END;
				case 20: return KICK_OFF;
				default: return null;
			}
		}
		
		public static int getIntValue(DataType dataType)
		{
			return dataType.mIntValue;
		}
		
		private DataType(int value)
		{
			mIntValue = value;
		}
		
		private int mIntValue = -1;
	};
	
	public NetworkeventData()
	{
		// Nothing to do yet
	}
	
	// Constructor to create object when sending data
	public NetworkeventData(DataType dataType, int x, int y, String level)
	{
		mDataType = dataType;
		mX = x;
		mY = y;
		mData = level.getBytes();
		mDataLength = mData.length;
	}
	
	// Constructor to create object when sending data
	public NetworkeventData(DataType dataType, Point position, String level)
	{
		mDataType = dataType;
		mX = position.x;
		mY = position.y;
		mData = level.getBytes();
		mDataLength = mData.length;
	}
	
	// Constructor to create object when receiving data
	public NetworkeventData(byte[] buffer)
	{
		ByteBuffer byteBuffer = ByteBuffer.wrap(buffer);
		
		mDataType = DataType.getEnumValue(byteBuffer.getInt());
		mX = byteBuffer.getInt();
		mY = byteBuffer.getInt();
		mDataLength = byteBuffer.getInt();
		if(0 < mDataLength)
		{
			mData = new byte[buffer.length - byteBuffer.arrayOffset()];
			byteBuffer.get(mData);
		}
	}
	
	public DataType getDataType()
	{
		return mDataType;
	}
	
	public void setDataType(DataType dataType)
	{
		mDataType = dataType;
	}
	
	public int getX()
	{
		return mX;
	}
	
	public void setX(int x)
	{
		mX = x;
	}
	
	public int getY()
	{
		return mY;
	}
	
	public void setY(int y)
	{
		mY = y;
	}
	
	public int getDataLength()
	{
		return mDataLength;
	}
	
	public String getStringData()
	{
		return new String(mData);
	}
	
	public byte[] getRawData()
	{
		return mData;
	}
	
	public void setStringData(String data)
	{
		mData = data.getBytes();
		mDataLength = mData.length;
	}
	
	public void setRawData(byte[] data)
	{
		mData = data;
		mDataLength = mData.length;
	}
	
	public boolean read(Socket socket) throws IOException
	{
		// Enough space for four integers
		byte[] buffer = new byte[16];
		BufferedInputStream in = new BufferedInputStream(socket.getInputStream());
		ByteBuffer byteBuffer = null;

		in.read(buffer);
		byteBuffer = ByteBuffer.wrap(buffer);
		mDataType = DataType.getEnumValue(byteBuffer.getInt());
		mX = byteBuffer.getInt();
		mY = byteBuffer.getInt();
		mDataLength = byteBuffer.getInt();
		// If there is no more data to read just return
		if(0 == mDataLength)
			return true;
		buffer = new byte[mDataLength];
		in.read(buffer);
		this.setRawData(buffer);

		return true;
	}
	
	public boolean write(Socket socket) throws IOException
	{
		// Enough space for four integers
		ByteBuffer byteBuffer = ByteBuffer.allocate(16);
		BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());

		byteBuffer.putInt(DataType.getIntValue(mDataType));
		byteBuffer.putInt(mX);
		byteBuffer.putInt(mY);
		byteBuffer.putInt(mDataLength);
		out.write(byteBuffer.array());
		// If there is no more data to send just return
		if(0 == mDataLength)
			return true;
		out.write(mData);

		return true;
	}
	
	/**
	 * This method rejects the connection attempt
	 *
	 * This methods accepts the incoming connection but immediately sends a CONNECTING_REJECT packet
	 * and disconnects the client
	 *
	 * @param serverSocket The socket to accept the connection on
	 */
	public void rejectConnection(Socket socket) throws IOException
	{
		// Enough space for four integers
		ByteBuffer byteBuffer = ByteBuffer.allocate(16);
		BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
		
		byteBuffer.putInt(DataType.getIntValue(DataType.CONNECTING_REJECT));
		byteBuffer.putInt(-1);
		byteBuffer.putInt(-1);
		byteBuffer.putInt(0);
		out.write(byteBuffer.array());
		socket.close();
	}

	private DataType mDataType	= DataType.INVALID;
	private int mX				= -1;
	private int mY				= -1;
	private int mDataLength		= 0;
	// Any additional data - e.g.: The name of the level when a client is connected, or the part of the level at downloading
	private byte[] mData		= null;
}
